#include "cbuf.h"
#include <stdlib.h>
#include <string.h>
#include <assert.h>

#define CBUF_MEM_SIZE (cbuf->capacity * cbuf->elem_size)

void cbuf_init (Cbuf* cbuf, int elem_size, int initial_capacity)
{
	cbuf->capacity = initial_capacity;
	cbuf->mem = malloc (elem_size * cbuf->capacity);
	cbuf->elem_size = elem_size;
	cbuf->count = 0;
	cbuf->begin = cbuf->mem;
	cbuf->end = cbuf->mem;
}

void cbuf_cleanup (Cbuf* cbuf)
{
	assert(cbuf->mem);
	
	free (cbuf->mem);
	cbuf->mem = NULL;
	cbuf->capacity = 0;
	cbuf->elem_size = 0;
	cbuf->count = 0;
	cbuf->begin = NULL;
	cbuf->end = NULL;
}

void cbuf_push (Cbuf* cbuf, void* elem)
{
	assert(cbuf->mem);
	
	if (cbuf_full (cbuf)) { // enlarge buffer
		int new_capacity = cbuf->capacity * 2;
		cbuf->mem = realloc (cbuf->mem, cbuf->elem_size * new_capacity);
		int n_move = cbuf->mem + cbuf->elem_size * cbuf->capacity - cbuf->begin;
		void* dest = cbuf->begin + cbuf->elem_size * cbuf->capacity;
		memcpy (dest, cbuf->begin, n_move);
		cbuf->begin = dest;
		cbuf->capacity = new_capacity;
	}
	
	cbuf->count++;
	memcpy (cbuf->end, elem, cbuf->elem_size);
	cbuf->end += cbuf->elem_size;
	if ((cbuf->end - cbuf->mem) >= CBUF_MEM_SIZE) {
		cbuf->end = cbuf->mem;
	}
}

void* cbuf_pop (Cbuf* cbuf)
{
	assert(cbuf->mem);
	assert(cbuf->count > 0);
	
	cbuf->count--;
	void* elem = cbuf->begin;
	cbuf->begin = cbuf->begin + cbuf->elem_size;
	if ((cbuf->begin - cbuf->mem) >= CBUF_MEM_SIZE) {
		cbuf->begin = cbuf->mem;
	}
	return elem;
}

void* cbuf_elem (Cbuf* cbuf, int index)
{
	assert(cbuf->mem);
	assert((index >= 0) && (index < cbuf->capacity));
	
	void* elem = cbuf->begin + index * cbuf->elem_size;
	if ((elem - cbuf->mem) >= CBUF_MEM_SIZE) {
		elem -= CBUF_MEM_SIZE;
	}
	return elem;
}

bool cbuf_full (Cbuf* cbuf)
{
	assert(cbuf->mem);

	return ((cbuf->count > 0) && (cbuf->begin == cbuf->end));
}

void cbuf_clear (Cbuf* cbuf)
{
	assert(cbuf->mem);

	cbuf->count = 0;
	cbuf->begin = cbuf->mem;
	cbuf->end = cbuf->begin;
}

#ifdef TEST_CBUF
#include <stdio.h>

int main ()
{
	Cbuf buf;
	cbuf_init(&buf, sizeof(int), 5);
	
	printf("full? %d\n", cbuf_full(&buf));
	
	for (int i = 0; i < 4; i++) cbuf_push(&buf, &i);
	for (int i = 0; i < 2; i++) printf("%d\n",*CBUF_POP(&buf,int));
	for (int i = 4; i < 7; i++) cbuf_push(&buf, &i);
	
	printf("full? %d\n", cbuf_full(&buf));	
	
	for (int i = 0; i < 5; i++) printf("[%d] = %d\n",i,*CBUF_ELEM(&buf,i,int));
	
	for (int i = 3; i < 9; i++) cbuf_push(&buf, &i);
	
	for (int i = 5; i < 11; i++) printf("[%d] = %d\n",i,*CBUF_ELEM(&buf,i,int));
	
	for (int i = 0; i < 11; i++) printf("%d\n",*CBUF_POP(&buf,int));
	
	cbuf_cleanup(&buf);
	return 0;
}

#endif
